/*
 * CPU.c
 *
 *  Created on: Mar 12, 2012
 *      Author: Tosshunter
 */
#include <stdio.h>
#include <stdlib.h>
#include "process.h"
#include <pthread.h>
#include "Scheduler.h"
#include <termios.h>
#include <unistd.h>
#define MAX_STEPS 10000
#define TIME_CYCLE 1000000
void *Timer(void *threadid);
void *DiskIO(void *inter);
void *KeyboardIO(void *inter);
int sharedMemory = 0;
pthread_mutex_t scheduler_lock;
pthread_mutex_t shared_mem_lock;
pthread_mutex_t cpu_lock;
pthread_mutex_t interrupt_lock;
int programCounter = 0;
pthread_t threads;
pthread_t timer_thread;
int numProcesses;
Queue initialProcesses;


PCBStr* initializeProcessType(int type) {
	ProcessStr *createdProcess;
	PCBStr *createdPCB;
	switch (type){
	case 1:
		createdProcess = createProcess(CALC);
		break;
	case 2:
		createdProcess = createProcess(PRODUCER);
		break;
	case 3:
		createdProcess = createProcess(CONSUMER);
		break;
	case 4:
		createdProcess = createProcess(UI);
		break;
	}
	createdPCB = createPCB(createdProcess);
	return createdPCB;
}

/*
 * Initializes the given amount of processes.
 */
void initializeProcesses(Scheduler *scheduler) {

	switch(scheduler->type) {
	case ROUND_ROBIN:
		while(!IsEmpty(initialProcesses)){
			int proc = FrontAndDequeue(initialProcesses);
			PCBStr *newPCB = initializeProcessType(proc);
			Enqueue(newPCB, scheduler->ready);
		}
		break;

	case PRIORITY:
		while(!IsEmpty(initialProcesses)){
			int proc = (int)FrontAndDequeue(initialProcesses);
			initializeProcessType(proc);
			PCBStr *newPCB = initializeProcessType(proc);
			switch(newPCB->process->priority){
			case 3:
				Enqueue(newPCB, scheduler->list[0]);
				break;
			case 2:
				Enqueue(newPCB, scheduler->list[1]);
				break;
			case 1:
				Enqueue(newPCB, scheduler->list[2]);
				break;
			case 0:
				Enqueue(newPCB, scheduler->list[3]);
				break;

			}
		}
		break;
	case LOTTERY:
		break;
	}

}


/*
 * Handles a system call from a process by saving its
 * instruction address and changing its status to blocked.
 * Receives the next process from schedule.
 */
void handleSystemCall(PCBStr *pcb, Scheduler *scheduler) {
	pthread_mutex_lock (&interrupt_lock);
	switch(pcb->process->type) {
	case CALC:
		pthread_create(&threads, NULL, DiskIO, &scheduler);
		pcb->state = BLOCKED;
		pcb->next_step = (pcb->next_step) + 1;
		pcb->waiting_on = 2;
		schedule(scheduler);
		PCBStr *nextPCB = scheduler->running;
		programCounter = nextPCB->next_step;
		break;
	case PRODUCER:
		pthread_mutex_lock(&shared_mem_lock);
		sharedMemory++;
		pthread_mutex_unlock(&shared_mem_lock);
		break;
	case CONSUMER:
		pthread_mutex_lock(&shared_mem_lock);
		PCBStr *pcb = scheduler->running;
				if (!sharedMemory >= 1) {
					pcb->state = BLOCKED;
					pcb->next_step = (pcb->next_step) + 1;
				} else {
					sharedMemory--;
				}
			pthread_mutex_unlock(&shared_mem_lock);
		break;
	case UI:
		pthread_create(&threads, NULL, KeyboardIO, &scheduler);
		pcb->state = BLOCKED;
		pcb->next_step = (pcb->next_step) + 1;
		pcb->waiting_on = 1;
		schedule(scheduler);
		PCBStr *newPCB = scheduler->running;
		programCounter = newPCB->next_step;
		break;

	}
	pthread_mutex_unlock (&interrupt_lock);
}


/*
 * Main method that begins the simulation and keeps it running.
 */
int main() {
	int schedule_type;
    int process_num;
    int i = 0;
    
    printf("Enter the type of scheduler:\n");
    printf("1. Round-robin\n");
    printf("2. Priority\n");
    printf("3. Lottery\n");
    scanf("%d", &schedule_type);
    printf("\n");
    
    printf("Enter how many processes to include:\n");
    scanf("%d", &process_num);
    printf("\n");
    int process_array[process_num];
    
    do {
        printf("Enter the type of process to add:\n");
        printf("1. Calc\n");
        printf("2. Producer\n");
        printf("3. Consumer\n");
        printf("4. UI\n");
        scanf("%d", &process_array[i]);
        printf("\n");
        if (process_array[i] > 4) {
            printf("Please enter a number 1-4.\n");
            printf("\n");
            continue;
        }
        i++;
    } while (i < process_num);
    
    numProcesses = 4;
	initialProcesses = createQueue(numProcesses);
	Scheduler *sch = createScheduler(ROUND_ROBIN);
	initializeProcesses(sch);
    PCBStr *current = sch->running;
    pthread_create(&timer_thread, NULL, Timer, sch);
	
    //infinitely loops through checking each step for a request
    while(programCounter % current->process->num_steps){
        int i;
        for (i = 0; i < current->process->num_requests; i++) {
            if (programCounter == current->process->requests[i]->step) {
                current->next_step = programCounter + 1;
                handleSystemCall(current, sch);
            }
        }
        programCounter++;
	}

	return 0;
}

/*
 * Get character from keyboard.
 */
int getch() {
    struct termios term_attr;
    tcflag_t old_c_lflag;
    int c;

    tcgetattr(STDIN_FILENO, &term_attr);
    old_c_lflag = term_attr.c_lflag;
    term_attr.c_lflag &= ~(ICANON|ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &term_attr);
    c = getchar();
    term_attr.c_lflag = old_c_lflag;
    tcsetattr(STDIN_FILENO, TCSANOW, &term_attr);
    return c;
}

/*
 * System timer that interrupts at constant intervals.
 */
void *Timer(void *inter)
{

   while(1)
   {

        usleep(TIME_CYCLE);
        pthread_mutex_lock (&interrupt_lock);
        Scheduler *sch = ( Scheduler *) inter ;
        PCBStr *pcb = sch->running;
        pcb->state = BLOCKED;
        pcb->next_step = (pcb->next_step) + 1;
        schedule(sch);
        PCBStr *newPCB = sch->running;
        programCounter = newPCB->next_step;
        pthread_mutex_unlock (&interrupt_lock);

   }

   pthread_exit(NULL);
}

/*
 * Disk device that wakes up after a random time when it is called.
 */
void *DiskIO(void *inter)
{
    usleep(rand()%(TIME_CYCLE*10) + TIME_CYCLE);
    pthread_mutex_lock (&interrupt_lock);
    Scheduler *sch = ( Scheduler *) inter ;
    PCBStr *pcb = sch->running;
    pcb->state = BLOCKED;
    pcb->next_step = (pcb->next_step) + 1;
    checkBlocked(sch, 2);
    PCBStr *newPCB = sch->running;
    programCounter = newPCB->next_step;
    pthread_mutex_unlock (&interrupt_lock);
    pthread_exit(NULL);
}

/*
 * Video device that wakes up after a random time when it is called.
 */
void *KeyboardIO(void *inter)
{
	usleep(rand()%(TIME_CYCLE*20) + TIME_CYCLE);
	pthread_mutex_lock (&interrupt_lock);
    char ch = getchar();
    printf("You pressed %c \n", ch);
    Scheduler *sch = ( Scheduler *) inter ;
    PCBStr *pcb = sch->running;
    pcb->state = BLOCKED;
    pcb->next_step = (pcb->next_step) + 1;
    checkBlocked(sch, 1);
    PCBStr *newPCB = sch->running;
    programCounter = newPCB->next_step;
    pthread_mutex_unlock (&interrupt_lock);
    pthread_exit(NULL);
}

void printStates(Scheduler *sch) {
    int i;
    for (i = 0; SOMETHING; i++) {
        printf("Process %d (Type: %d)'s state: %d);
    }
}
